# -*- coding: utf-8 -*-
# Author   : ZhangQing
# Time     : 2025-08-04 23:36
# File     : project_service.py
# Project  : codebuddy_craft
# Desc     : 项目管理服务

# backend/core/services/project_service.py
from typing import Dict, List, Optional, Any
import os
import json
import shutil
import asyncio
from datetime import datetime
from pathlib import Path
import zipfile
import tempfile

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update, delete
from pydantic import BaseModel, Field

from backend.models.project import Project
from backend.models.file import File as ProjectFile
from backend.models.user import User
from backend.database.connection import get_db_session
from backend.utils.security import get_current_user_id
from backend.utils.config import get_settings


class ProjectCreateRequest(BaseModel):
    """项目创建请求"""
    name: str = Field(description="项目名称")
    type: str = Field(description="项目类型", default="single")
    tech_stack: Optional[str] = Field(default="Python", description="技术栈")
    description: Optional[str] = Field(default="", description="项目描述")


class ProjectResponse(BaseModel):
    """项目响应模型"""
    id: str = Field(description="项目ID")
    name: str = Field(description="项目名称")
    type: str = Field(description="项目类型")
    tech_stack: str = Field(description="技术栈")
    description: str = Field(description="项目描述")
    created_at: datetime = Field(description="创建时间")
    updated_at: datetime = Field(description="更新时间")
    file_count: int = Field(description="文件数量")


class FileTreeNode(BaseModel):
    """文件树节点"""
    name: str = Field(description="文件/文件夹名称")
    path: str = Field(description="路径")
    type: str = Field(description="类型: file/folder")
    size: Optional[int] = Field(default=None, description="文件大小")
    children: Optional[List['FileTreeNode']] = Field(default=None, description="子节点")
    created_at: Optional[datetime] = Field(default=None, description="创建时间")
    updated_at: Optional[datetime] = Field(default=None, description="更新时间")


class ProjectService:
    """项目管理服务"""
    
    def __init__(self):
        self.settings = get_settings()
        self.upload_path = Path(self.settings.upload_path)
        self.upload_path.mkdir(exist_ok=True)
    
    async def create_project(
        self,
        request: ProjectCreateRequest,
        user_id: str
    ) -> ProjectResponse:
        """创建新项目"""
        try:
            async with get_db_session() as session:
                # 创建项目记录
                project = Project(
                    name=request.name,
                    type=request.type,
                    tech_stack=request.tech_stack,
                    description=request.description,
                    user_id=user_id,
                    created_at=datetime.utcnow(),
                    updated_at=datetime.utcnow()
                )
                
                session.add(project)
                await session.flush()
                
                # 创建项目目录
                project_dir = self.upload_path / str(project.id)
                project_dir.mkdir(exist_ok=True)
                
                # 根据项目类型创建初始文件
                await self._create_initial_files(project, project_dir, session)
                
                await session.commit()
                
                # 统计文件数量
                file_count = await self._count_project_files(project.id, session)
                
                return ProjectResponse(
                    id=str(project.id),
                    name=project.name,
                    type=project.type,
                    tech_stack=project.tech_stack,
                    description=project.description,
                    created_at=project.created_at,
                    updated_at=project.updated_at,
                    file_count=file_count
                )
        
        except Exception as e:
            raise Exception(f"创建项目失败: {str(e)}")
    
    async def get_project(self, project_id: str, user_id: str) -> ProjectResponse:
        """获取项目信息"""
        try:
            async with get_db_session() as session:
                # 查询项目
                result = await session.execute(
                    select(Project).where(
                        Project.id == project_id,
                        Project.user_id == user_id
                    )
                )
                project = result.scalar_one_or_none()
                
                if not project:
                    raise Exception("项目不存在或无权限访问")
                
                # 统计文件数量
                file_count = await self._count_project_files(project_id, session)
                
                return ProjectResponse(
                    id=str(project.id),
                    name=project.name,
                    type=project.type,
                    tech_stack=project.tech_stack,
                    description=project.description,
                    created_at=project.created_at,
                    updated_at=project.updated_at,
                    file_count=file_count
                )
        
        except Exception as e:
            raise Exception(f"获取项目失败: {str(e)}")
    
    async def list_projects(
        self, 
        user_id: str,
        page: int = 1,
        size: int = 10
    ) -> Dict[str, Any]:
        """获取用户项目列表"""
        try:
            async with get_db_session() as session:
                # 计算偏移量
                offset = (page - 1) * size
                
                # 查询项目列表
                result = await session.execute(
                    select(Project)
                    .where(Project.user_id == user_id)
                    .order_by(Project.updated_at.desc())
                    .offset(offset)
                    .limit(size)
                )
                projects = result.scalars().all()
                
                # 查询总数
                count_result = await session.execute(
                    select(Project.id).where(Project.user_id == user_id)
                )
                total = len(count_result.scalars().all())
                
                # 转换为响应格式
                project_list = []
                for project in projects:
                    file_count = await self._count_project_files(str(project.id), session)
                    project_list.append(ProjectResponse(
                        id=str(project.id),
                        name=project.name,
                        type=project.type,
                        tech_stack=project.tech_stack,
                        description=project.description,
                        created_at=project.created_at,
                        updated_at=project.updated_at,
                        file_count=file_count
                    ))
                
                return {
                    "projects": project_list,
                    "total": total,
                    "page": page,
                    "size": size,
                    "pages": (total + size - 1) // size
                }
        
        except Exception as e:
            raise Exception(f"获取项目列表失败: {str(e)}")
    
    async def get_file_tree(self, project_id: str, user_id: str) -> List[FileTreeNode]:
        """获取项目文件树"""
        try:
            async with get_db_session() as session:
                # 验证项目权限
                await self._verify_project_access(project_id, user_id, session)
                
                # 获取项目文件
                result = await session.execute(
                    select(ProjectFile)
                    .where(ProjectFile.project_id == project_id)
                    .order_by(ProjectFile.path)
                )
                files = result.scalars().all()
                
                # 构建文件树
                return self._build_file_tree(files)
        
        except Exception as e:
            raise Exception(f"获取文件树失败: {str(e)}")
    
    async def create_file(
        self,
        project_id: str,
        file_path: str,
        content: str,
        user_id: str
    ) -> str:
        """创建文件"""
        try:
            async with get_db_session() as session:
                # 验证项目权限
                await self._verify_project_access(project_id, user_id, session)
                
                # 检查文件是否已存在
                existing_file = await session.execute(
                    select(ProjectFile).where(
                        ProjectFile.project_id == project_id,
                        ProjectFile.path == file_path
                    )
                )
                if existing_file.scalar_one_or_none():
                    raise Exception("文件已存在")
                
                # 创建物理文件
                project_dir = self.upload_path / project_id
                full_path = project_dir / file_path
                full_path.parent.mkdir(parents=True, exist_ok=True)
                
                with open(full_path, 'w', encoding='utf-8') as f:
                    f.write(content)
                
                # 创建数据库记录
                project_file = ProjectFile(
                    project_id=project_id,
                    path=file_path,
                    name=Path(file_path).name,
                    size=len(content.encode('utf-8')),
                    content_hash=self._calculate_hash(content),
                    created_at=datetime.utcnow(),
                    updated_at=datetime.utcnow()
                )
                
                session.add(project_file)
                
                # 更新项目修改时间
                await session.execute(
                    update(Project)
                    .where(Project.id == project_id)
                    .values(updated_at=datetime.utcnow())
                )
                
                await session.commit()
                
                return str(project_file.id)
        
        except Exception as e:
            raise Exception(f"创建文件失败: {str(e)}")
    
    async def save_file(
        self,
        file_id: str,
        content: str,
        user_id: str
    ) -> bool:
        """保存文件"""
        try:
            async with get_db_session() as session:
                # 获取文件信息
                result = await session.execute(
                    select(ProjectFile, Project)
                    .join(Project, ProjectFile.project_id == Project.id)
                    .where(
                        ProjectFile.id == file_id,
                        Project.user_id == user_id
                    )
                )
                file_project = result.first()
                
                if not file_project:
                    raise Exception("文件不存在或无权限访问")
                
                project_file, project = file_project
                
                # 保存物理文件
                project_dir = self.upload_path / str(project.id)
                full_path = project_dir / project_file.path
                
                with open(full_path, 'w', encoding='utf-8') as f:
                    f.write(content)
                
                # 更新数据库记录
                new_hash = self._calculate_hash(content)
                await session.execute(
                    update(ProjectFile)
                    .where(ProjectFile.id == file_id)
                    .values(
                        size=len(content.encode('utf-8')),
                        content_hash=new_hash,
                        updated_at=datetime.utcnow()
                    )
                )
                
                # 更新项目修改时间
                await session.execute(
                    update(Project)
                    .where(Project.id == project.id)
                    .values(updated_at=datetime.utcnow())
                )
                
                await session.commit()
                
                return True
        
        except Exception as e:
            raise Exception(f"保存文件失败: {str(e)}")
    
    async def get_file_content(
        self,
        file_id: str,
        user_id: str
    ) -> Dict[str, Any]:
        """获取文件内容"""
        try:
            async with get_db_session() as session:
                # 获取文件信息
                result = await session.execute(
                    select(ProjectFile, Project)
                    .join(Project, ProjectFile.project_id == Project.id)
                    .where(
                        ProjectFile.id == file_id,
                        Project.user_id == user_id
                    )
                )
                file_project = result.first()
                
                if not file_project:
                    raise Exception("文件不存在或无权限访问")
                
                project_file, project = file_project
                
                # 读取物理文件内容
                project_dir = self.upload_path / str(project.id)
                full_path = project_dir / project_file.path
                
                if not full_path.exists():
                    raise Exception("物理文件不存在")
                
                with open(full_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                
                return {
                    "id": str(project_file.id),
                    "name": project_file.name,
                    "path": project_file.path,
                    "content": content,
                    "size": project_file.size,
                    "created_at": project_file.created_at,
                    "updated_at": project_file.updated_at
                }
        
        except Exception as e:
            raise Exception(f"获取文件内容失败: {str(e)}")
    
    async def delete_file(
        self,
        file_id: str,
        user_id: str
    ) -> bool:
        """删除文件"""
        try:
            async with get_db_session() as session:
                # 获取文件信息
                result = await session.execute(
                    select(ProjectFile, Project)
                    .join(Project, ProjectFile.project_id == Project.id)
                    .where(
                        ProjectFile.id == file_id,
                        Project.user_id == user_id
                    )
                )
                file_project = result.first()
                
                if not file_project:
                    raise Exception("文件不存在或无权限访问")
                
                project_file, project = file_project
                
                # 删除物理文件
                project_dir = self.upload_path / str(project.id)
                full_path = project_dir / project_file.path
                
                if full_path.exists():
                    full_path.unlink()
                
                # 删除数据库记录
                await session.execute(
                    delete(ProjectFile).where(ProjectFile.id == file_id)
                )
                
                # 更新项目修改时间
                await session.execute(
                    update(Project)
                    .where(Project.id == project.id)
                    .values(updated_at=datetime.utcnow())
                )
                
                await session.commit()
                
                return True
        
        except Exception as e:
            raise Exception(f"删除文件失败: {str(e)}")
    
    async def export_project(
        self,
        project_id: str,
        user_id: str,
        export_type: str = "zip"
    ) -> str:
        """导出项目"""
        try:
            async with get_db_session() as session:
                # 验证项目权限
                project = await self._verify_project_access(project_id, user_id, session)
                
                if export_type == "zip":
                    return await self._export_as_zip(project, session)
                else:
                    raise Exception(f"不支持的导出类型: {export_type}")
        
        except Exception as e:
            raise Exception(f"导出项目失败: {str(e)}")
    
    async def _create_initial_files(
        self,
        project: Project,
        project_dir: Path,
        session: AsyncSession
    ):
        """创建初始文件"""
        if project.type == "single":
            # 单文件项目
            file_extension = self._get_file_extension(project.tech_stack)
            filename = f"main{file_extension}"
            initial_content = self._get_initial_content(project.tech_stack)
            
            # 创建物理文件
            file_path = project_dir / filename
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(initial_content)
            
            # 创建数据库记录
            project_file = ProjectFile(
                project_id=str(project.id),
                path=filename,
                name=filename,
                size=len(initial_content.encode('utf-8')),
                content_hash=self._calculate_hash(initial_content),
                created_at=datetime.utcnow(),
                updated_at=datetime.utcnow()
            )
            session.add(project_file)
        
        elif project.type == "multi":
            # 多文件项目
            await self._create_multi_file_structure(project, project_dir, session)
    
    async def _create_multi_file_structure(
        self,
        project: Project,
        project_dir: Path,
        session: AsyncSession
    ):
        """创建多文件项目结构"""
        tech_stack = project.tech_stack.lower()
        
        if "python" in tech_stack:
            # Python项目结构
            structure = {
                "main-v0.0.py": "# 主程序入口\n\nif __name__ == '__main__':\n    print('Hello, CodeBuddy Craft!')\n",
                "requirements.txt": "# 项目依赖\n",
                "README.md": f"# {project.name}\n\n{project.description}\n",
                "src/__init__.py": "",
                "tests/__init__.py": "",
                "tests/test_main.py": "# 测试文件\nimport unittest\n\nclass TestMain(unittest.TestCase):\n    def test_example(self):\n        self.assertTrue(True)\n"
            }
        
        elif "javascript" in tech_stack or "node" in tech_stack:
            # JavaScript/Node.js项目结构
            structure = {
                "index.js": "// 主程序入口\nconsole.log('Hello, CodeBuddy Craft!');\n",
                "package.json": json.dumps({
                    "name": project.name.lower().replace(" ", "-"),
                    "version": "1.0.0",
                    "description": project.description,
                    "main": "index.js",
                    "scripts": {"start": "node index.js"},
                    "dependencies": {}
                }, indent=2),
                "README.md": f"# {project.name}\n\n{project.description}\n",
                "src/app.js": "// 应用逻辑\n",
                "tests/app.test.js": "// 测试文件\n"
            }
        
        elif "react" in tech_stack:
            # React项目结构
            structure = {
                "public/index.html": "<!DOCTYPE html>\n<html>\n<head>\n    <title>React App</title>\n</head>\n<body>\n    <div id='root'></div>\n</body>\n</html>",
                "src/index.js": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\n\nReactDOM.render(<App />, document.getElementById('root'));",
                "src/App.js": "import React from 'react';\n\nfunction App() {\n  return (\n    <div>\n      <h1>Hello, CodeBuddy Craft!</h1>\n    </div>\n  );\n}\n\nexport default App;",
                "package.json": json.dumps({
                    "name": project.name.lower().replace(" ", "-"),
                    "version": "1.0.0",
                    "dependencies": {
                        "react": "^18.0.0",
                        "react-dom": "^18.0.0"
                    }
                }, indent=2),
                "README.md": f"# {project.name}\n\n{project.description}\n"
            }
        
        else:
            # 默认结构
            structure = {
                "README.md": f"# {project.name}\n\n{project.description}\n",
                "main.txt": "# 项目文件\n"
            }
        
        # 创建文件和目录
        for file_path, content in structure.items():
            full_path = project_dir / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            
            with open(full_path, 'w', encoding='utf-8') as f:
                f.write(content)
            
            # 创建数据库记录
            project_file = ProjectFile(
                project_id=str(project.id),
                path=file_path,
                name=Path(file_path).name,
                size=len(content.encode('utf-8')),
                content_hash=self._calculate_hash(content),
                created_at=datetime.utcnow(),
                updated_at=datetime.utcnow()
            )
            session.add(project_file)
    
    def _get_file_extension(self, tech_stack: str) -> str:
        """根据技术栈获取文件扩展名"""
        tech_stack = tech_stack.lower()
        
        if "python" in tech_stack:
            return ".py"
        elif "javascript" in tech_stack or "node" in tech_stack:
            return ".js"
        elif "typescript" in tech_stack:
            return ".ts"
        elif "java" in tech_stack:
            return ".java"
        elif "cpp" in tech_stack or "c++" in tech_stack:
            return ".cpp"
        elif "go" in tech_stack:
            return ".go"
        elif "rust" in tech_stack:
            return ".rs"
        else:
            return ".txt"
    
    def _get_initial_content(self, tech_stack: str) -> str:
        """根据技术栈获取初始代码内容"""
        tech_stack = tech_stack.lower()
        
        if "python" in tech_stack:
            return "# CodeBuddy Craft Python 项目\n\ndef main():\n    print('Hello, CodeBuddy Craft!')\n\nif __name__ == '__main__':\n    main()\n"
        elif "javascript" in tech_stack:
            return "// CodeBuddy Craft JavaScript 项目\n\nfunction main() {\n    console.log('Hello, CodeBuddy Craft!');\n}\n\nmain();\n"
        elif "java" in tech_stack:
            return "// CodeBuddy Craft Java 项目\n\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(\"Hello, CodeBuddy Craft!\");\n    }\n}\n"
        else:
            return f"# CodeBuddy Craft {tech_stack} 项目\n\nprint('Hello, CodeBuddy Craft!')\n"
    
    def _calculate_hash(self, content: str) -> str:
        """计算内容哈希"""
        import hashlib
        return hashlib.md5(content.encode('utf-8')).hexdigest()
    
    async def _count_project_files(self, project_id: str, session: AsyncSession) -> int:
        """统计项目文件数量"""
        result = await session.execute(
            select(ProjectFile.id).where(ProjectFile.project_id == project_id)
        )
        return len(result.scalars().all())
    
    async def _verify_project_access(
        self,
        project_id: str,
        user_id: str,
        session: AsyncSession
    ) -> Project:
        """验证项目访问权限"""
        result = await session.execute(
            select(Project).where(
                Project.id == project_id,
                Project.user_id == user_id
            )
        )
        project = result.scalar_one_or_none()
        
        if not project:
            raise Exception("项目不存在或无权限访问")
        
        return project
    
    def _build_file_tree(self, files: List[ProjectFile]) -> List[FileTreeNode]:
        """构建文件树"""
        tree = {}
        
        for file in files:
            parts = file.path.split('/')
            current = tree
            
            for i, part in enumerate(parts):
                if part not in current:
                    is_file = i == len(parts) - 1
                    current[part] = {
                        'name': part,
                        'path': '/'.join(parts[:i+1]),
                        'type': 'file' if is_file else 'folder',
                        'children': {} if not is_file else None,
                        'size': file.size if is_file else None,
                        'created_at': file.created_at if is_file else None,
                        'updated_at': file.updated_at if is_file else None
                    }
                current = current[part]['children'] if not is_file else current
        
        return self._convert_tree_to_nodes(tree)
    
    def _convert_tree_to_nodes(self, tree: Dict) -> List[FileTreeNode]:
        """转换树结构为节点列表"""
        nodes = []
        
        for name, node_data in tree.items():
            children = None
            if node_data['children'] is not None:
                children = self._convert_tree_to_nodes(node_data['children'])
            
            node = FileTreeNode(
                name=node_data['name'],
                path=node_data['path'],
                type=node_data['type'],
                size=node_data['size'],
                children=children,
                created_at=node_data['created_at'],
                updated_at=node_data['updated_at']
            )
            nodes.append(node)
        
        # 排序：文件夹在前，文件在后
        nodes.sort(key=lambda x: (x.type == 'file', x.name.lower()))
        
        return nodes
    
    async def _export_as_zip(self, project: Project, session: AsyncSession) -> str:
        """导出为ZIP文件"""
        # 创建临时ZIP文件
        temp_dir = Path(tempfile.mkdtemp())
        zip_path = temp_dir / f"{project.name}.zip"
        
        try:
            project_dir = self.upload_path / str(project.id)
            
            with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                for file_path in project_dir.rglob('*'):
                    if file_path.is_file():
                        arcname = file_path.relative_to(project_dir)
                        zipf.write(file_path, arcname)
            
            # 这里应该将ZIP文件上传到对象存储，并返回下载URL
            # 暂时返回本地路径
            return str(zip_path)
        
        except Exception as e:
            # 清理临时文件
            if zip_path.exists():
                zip_path.unlink()
            raise e


# 创建全局实例
project_service = ProjectService()
